package com.zhao.dota.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhao.dota.bo.AuthenticateUser;
import com.zhao.dota.contant.CacheConstant;
import com.zhao.dota.controller.WebSocketServer;
import com.zhao.dota.exception.LeagueException;
import com.zhao.dota.mapper.QuotationMapper;
import com.zhao.dota.model.*;
import com.zhao.dota.security.SecurityUtil;
import com.zhao.dota.service.*;
import com.zhao.dota.struct.WsMessage;
import com.zhao.dota.vo.QuotationVo;
import com.zhao.dota.vo.RollVo;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@CacheConfig(cacheNames = CacheConstant.ABILITY)
public class QuotationServiceImpl extends ServiceImpl<QuotationMapper, Quotation> implements QuotationService {

    @Resource
    private HeroService heroService;
    @Resource
    private UserHeroService userHeroService;
    @Resource
    private UserGoldService userGoldService;
    @Resource
    private LeagueService leagueService;
    @Resource
    private WebSocketServer webSocketServer;
    @Resource
    private QuotationService quotationService;
    @Resource
    private RoundService roundService;

    @Override
    @Transactional
    public Quotation save(Integer leagueId, Quotation quotation) {
        AuthenticateUser authenticateUser = SecurityUtil.getAuthenticateUser();
        return quotation(leagueId, quotation, authenticateUser.getId());
    }

    @Override
    @Transactional
    public Quotation otherQuotation(Integer leagueId, Quotation quotation) {
        return quotation(leagueId, quotation, quotation.getUserId());
    }

    private Quotation quotation(Integer leagueId, Quotation quotation, Integer userId) {
        Round round = roundService.getById(quotation.getRoundId());
        if (round.getType() == Round.Type.SECRET_AUCTION) {
            return secretAuction(leagueId, quotation, userId);
        }
        return null;
    }

    private Quotation auction(Integer leagueId, Quotation quotation, Integer userId) {
        QueryWrapper<Quotation> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("max(gold) as gold")
                .eq("hero_id", quotation.getHeroId())
                .eq("round_id", quotation.getRoundId())
                .ne("user_id", userId);

        Quotation max = getOne(queryWrapper);
        if (max != null && quotation.getGold() <= max.getGold()) {
            throw new LeagueException("小于当前价格，报价失败");
        }
        return secretAuction(leagueId, quotation, userId);
    }

    private Quotation secretAuction(Integer leagueId, Quotation quotation, Integer userId) {
        League league = leagueService.getById(leagueId);
        UserGold gold = userGoldService.getByLeagueIdUserId(leagueId, userId);
        Quotation old = checkOld(quotation, userId);
        if (old == null) {
            if (quotation.getGold() > gold.getGold() && quotation.getGold() > league.getHeroPrice()) {
                throw new LeagueException("余额不足，请修改报价金额");
            }
            gold.setGold(gold.getGold() - quotation.getGold());
            quotation.setLeagueId(leagueId);
            quotation.setUserId(userId);
            quotation.setTime(LocalDateTime.now());

            Hero hero = heroService.getById(quotation.getHeroId());
            quotation.setHeroName(hero.getName());
            quotation.setHeroLocalizedName(hero.getLocalizedName());
        } else {
            // 不是同一个英雄不能改;价格相同不需要改。修改英雄用删除功能
            if (!Objects.equals(old.getHeroId(), quotation.getHeroId()) ||
                    Objects.equals(old.getGold(), quotation.getGold())) {
                return old;
            }
            int diff = quotation.getGold() - old.getGold();
            if (diff > gold.getGold()) {
                throw new LeagueException("余额不足，请修改报价金额");
            }
            gold.setGold(gold.getGold() - diff);
            old.setGold(quotation.getGold());
            quotation = old;
        }
        userGoldService.updateById(gold);
        saveOrUpdate(quotation);
        return quotation;
    }

    private Quotation checkOld(Quotation quotation, Integer userId) {
        if (quotation.getId() != null) {
            return getById(quotation.getId());
        }
        LambdaQueryWrapper<Quotation> query = Wrappers.lambdaQuery(Quotation.class)
                .eq(Quotation::getLeagueId, quotation.getLeagueId())
                .eq(Quotation::getRoundId, quotation.getRoundId())
                .eq(Quotation::getHeroId, quotation.getHeroId())
                .eq(Quotation::getUserId, userId);
        return getOne(query);
    }

    @Override
    public List<QuotationVo> findByRoundId(Integer roundId) {
        return baseMapper.findByRoundId(roundId);
    }

    @Override
    @Transactional
    public boolean calUserHeroes(Round quoting) {
        LambdaQueryWrapper<Quotation> query = Wrappers.lambdaQuery(Quotation.class).eq(Quotation::getRoundId, quoting.getId());
        List<Quotation> list = list(query);
        if (quoting.getType() == Round.Type.SECRET_AUCTION) {
            return calAuctionUserHero(quoting, list);
        }
        return false;
    }

    private boolean calAuctionUserHero(Round quoting, List<Quotation> list) {
        Map<Integer, List<Quotation>> heroMap = list.stream().collect(Collectors.groupingBy(Quotation::getHeroId));
        List<Quotation> success = new ArrayList<>();
        List<Quotation> equal = new ArrayList<>();
        List<Quotation> fail = new ArrayList<>();
        for (List<Quotation> quotations : heroMap.values()) {
            if (quotations.size() == 1) {
                success.add(quotations.get(0));
                continue;
            }
            Collections.sort(quotations);
            Collections.reverse(quotations);
            Quotation first = quotations.get(0);
            int max = first.getGold();
            boolean eq = false;
            for (int i = 1; i < quotations.size(); i++) {
                Quotation quotation = quotations.get(i);
                if (quotation.getGold() < max) {
                    fail.add(quotation);
                } else {
                    eq = true;
                    equal.add(quotation);
                }
            }
            if (eq) {
                equal.add(first);
            } else {
                success.add(first);
            }
        }
        List<Quotation> quotations = new ArrayList<>();
        if (!success.isEmpty()) {
            for (Quotation quotation : success) {
                quotation.setSucceed(true);
                quotations.add(quotation);
                userHeroService.getHero(quotation);
            }
        }
        if (!fail.isEmpty()) {
            for (Quotation quotation : fail) {
                quotation.setSucceed(false);
                quotations.add(quotation);
                // 退钱
                refund(quotation);
            }
        }
        saveOrUpdateBatch(quotations);
        return !equal.isEmpty();
    }

    @Override
    public int refund(Quotation quotation) {
        UserGold gold = userGoldService.getByLeagueIdUserId(quotation.getLeagueId(), quotation.getUserId());
        gold.setGold(gold.getGold() + quotation.getGold());
        return userGoldService.updateById(gold) ? 1 : 0;
    }

    @Override
    public void sendRollWebSocket(Integer roundId) {
        LambdaQueryWrapper<Quotation> query = Wrappers.lambdaQuery(Quotation.class)
                .isNull(Quotation::getSucceed)
                .isNull(Quotation::getPoint)
                .eq(Quotation::getRoundId, roundId);
        List<Quotation> equal = quotationService.list(query);
        Map<Integer, List<RollVo>> collect = equal.stream().map(RollVo::new).collect(Collectors.groupingBy(RollVo::getUserId));
        for (Map.Entry<Integer, List<RollVo>> entry : collect.entrySet()) {
            WsMessage<List<RollVo>> wsMessage = new WsMessage<>();
            wsMessage.setData(entry.getValue());
            wsMessage.setAction("roll");
            webSocketServer.sendUserMessage(entry.getKey(), wsMessage);
        }
    }

    @Override
    public void systemRoll(Integer roundId) {
        LambdaQueryWrapper<Quotation> query = Wrappers.lambdaQuery(Quotation.class)
                .isNull(Quotation::getSucceed)
                .isNull(Quotation::getPoint)
                .eq(Quotation::getRoundId, roundId);
        List<Quotation> quotations = quotationService.list(query);
        for (Quotation quotation : quotations) {
            roll(quotation);
        }
    }

    @Override
    public int roll(Integer roundId, Integer heroId) {
        AuthenticateUser authenticateUser = SecurityUtil.getAuthenticateUser();
        LambdaQueryWrapper<Quotation> query = Wrappers.lambdaQuery(Quotation.class)
                .eq(Quotation::getRoundId, roundId)
                .eq(Quotation::getHeroId, heroId)
                .eq(Quotation::getUserId, authenticateUser.getId());
        Quotation quotation = quotationService.getOne(query);
        if (quotation.getPoint() != null) {
            return quotation.getPoint();
        }
        return roll(quotation);
    }

    @Override
    public List<RollVo> needRolls(Integer leagueId) {
        LambdaQueryWrapper<Round> query = Wrappers.lambdaQuery(Round.class)
                .eq(Round::getLeagueId, leagueId)
                .eq(Round::getState, Round.State.ROLL);
        Round round = roundService.getOne(query);
        if (round == null) {
            return Collections.emptyList();
        }
        AuthenticateUser authenticateUser = SecurityUtil.getAuthenticateUser();
        LambdaQueryWrapper<Quotation> rollQuery = Wrappers.lambdaQuery(Quotation.class)
                .isNull(Quotation::getSucceed)
                .isNull(Quotation::getPoint)
                .eq(Quotation::getRoundId, round.getId())
                .eq(Quotation::getUserId, authenticateUser.getId());
        List<Quotation> quotations = list(rollQuery);
        return quotations.stream().map(RollVo::new).collect(Collectors.toList());
    }

    @Override
    public boolean cancel(Integer id) {
        Quotation quotation = getById(id);
        if (quotation.getSucceed() == null) {
            return removeById(id);
        }
        return false;
    }

    private int roll(Quotation quotation) {
        int point;
        LambdaQueryWrapper<Quotation> query = Wrappers.lambdaQuery(Quotation.class)
                .isNull(Quotation::getSucceed)
                .isNotNull(Quotation::getPoint)
                .eq(Quotation::getRoundId, quotation.getRoundId())
                .eq(Quotation::getHeroId, quotation.getHeroId());
        List<Quotation> list = list(query);
        Set<Integer> pointSet = list.stream().map(Quotation::getPoint).collect(Collectors.toSet());
        do {
            point = RandomUtil.randomInt(100) + 1;
        } while (pointSet.contains(point));
        quotation.setPoint(point);
        quotationService.updateById(quotation);
        return point;
    }

}
